import { extname } from "node:path";

export function dependenciesEqual(pLeftDependency) {
  // As we're using this to compare (typescript) pre-compilation dependencies
  // with post-compilation dependencies we do not consider the moduleSystem.
  //
  // In typescript the module system will typically be es6. Compiled down to
  // javascript it can be es6, but also cjs (depends on the `module` setting
  // in your tsconfig). In the latter case, we're still looking at the same
  // dependency even though the module systems differ.
  return (pRightDependency) =>
    pLeftDependency.module === pRightDependency.module &&
    pLeftDependency.dynamic === pRightDependency.dynamic &&
    pLeftDependency.exoticRequire === pRightDependency.exoticRequire;
}

export function detectPreCompilationNess(pTSDependencies, pJSDependencies) {
  return pTSDependencies.map((pTSDependency) =>
    pJSDependencies.some(dependenciesEqual(pTSDependency))
      ? { ...pTSDependency, preCompilationOnly: false }
      : {
          ...pTSDependency,
          preCompilationOnly: true,
          dependencyTypes: (pTSDependency.dependencyTypes || []).concat(
            "pre-compilation-only",
          ),
        },
  );
}

const PROTOCOL_ONLY_BUILTINS = new Set([
  // module.builtinModules.filter(m=>m.startsWith('node:'))
  "node:sea",
  "node:sqlite",
  "node:test",
  "node:test/reporters",
  // module.builtinModules.filter(m=>m.startsWith('bun:'))
  "bun:ffi",
  "bun:jsc",
  "bun:sqlite",
  "bun:test",
  "bun:wrap",
]);

/**
 * Given a module name returns the canonical module name.
 * @param {string} pProtocol
 * @param {string} pModuleName
 * @returns {string}
 */
function getCanonicalModuleName(pModuleName, pProtocol) {
  const lModuleWithProtocol = pProtocol + pModuleName;
  const lIsJsIshProtocol = pProtocol === "node:" || pProtocol === "bun:";
  const lIsProtocolOnlyModule = PROTOCOL_ONLY_BUILTINS.has(lModuleWithProtocol);

  if (!lIsJsIshProtocol || lIsProtocolOnlyModule) {
    return lModuleWithProtocol;
  }
  return pModuleName;
}

/**
 * Given a module string returns in an object
 * - the module name
 * - the protocol (when encoded in the string)
 * - the mimeType (when encoded in the string)
 *
 * See https://nodejs.org/api/esm.html#esm_urls
 *
 * would've loved to use url.URL here, but that doesn't extract the mime type
 * (if there's a default node API that does this I'm all ears)
 *
 * @param {string} pString
 * @returns {{module:string; protocol?:string; mimeType?:string}}
 */
// eslint-disable-next-line complexity
export function extractModuleAttributes(pString) {
  let lReturnValue = { module: pString };
  const lModuleAttributes = pString.match(
    // eslint-disable-next-line security/detect-unsafe-regex
    /^(?<protocol>node:|file:|data:|bun:)(?:(?<mimeType>[^,]+),)?(?<module>.+)$/,
  );

  if (lModuleAttributes?.groups) {
    lReturnValue.module = getCanonicalModuleName(
      lModuleAttributes?.groups.module,
      lModuleAttributes?.groups.protocol,
    );
    if (lModuleAttributes?.groups?.protocol) {
      lReturnValue.protocol = lModuleAttributes.groups.protocol;
    }
    if (lModuleAttributes?.groups?.mimeType) {
      lReturnValue.mimeType = lModuleAttributes.groups.mimeType;
    }
  }

  return lReturnValue;
}

/**
 * returns pFilenameString stripped of any 'query parameters' e.g.
 *
 * "hello/there?thing=smurf" => "hello/there"
 * "boink/boink/t.gif?resource" => "boink/boink/t.gif"
 * "no/thing/after.js" => "no/thing/after.js"
 *
 * @param {string} pFilenameString string to strip the query parameters from
 * @returns {string} the stripped string
 */
export function stripQueryParameters(pFilenameString) {
  // url.parse(pFilenameString).pathname did this quite admirably, but it's
  // deprecated, hence this funky RE replace. And accompanying unit test :-/
  return pFilenameString.replace(/\?.+$/, "");
}

const TS_COMPATIBLE_EXTENSIONS = new Set([
  ".ts",
  ".tsx",
  ".mts",
  ".cts",
  ".js",
  ".mjs",
  ".cjs",
  ".vue",
]);

/**
 * Returns true if the file name has a TypeScript compatible extension
 * @param {string} pFileName
 * @returns {boolean}
 */
export function isTypeScriptCompatible(pFileName) {
  return TS_COMPATIBLE_EXTENSIONS.has(extname(pFileName));
}
